package io.github.zerobyteword.springfasterstartup;

import io.github.zerobyteword.springfasterstartup.config.AsyncInitProperties;
import io.github.zerobyteword.springfasterstartup.metrics.BeanInitTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.AntPathMatcher;

import java.lang.annotation.Annotation;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BooleanSupplier;
import java.util.stream.Collectors;

/**
 * @author zhaoliu
 */
public class AsyncInitManager {
    public static final AntPathMatcher antPathMatcher = new AntPathMatcher();
    private static final Logger log = LoggerFactory.getLogger(AsyncInitManager.class);
    private final List<AsyncFilter> includeFilters;

    private final List<AsyncFilter> innerIncludeFilters;
    private final Map<Class<?>, Boolean> initClassCacheMap = new ConcurrentHashMap<>(256);
    private final List<AsyncFilter> excludeFilters;
    private final AtomicBoolean closeFlag = new AtomicBoolean(false);
    private final List<AsyncInitBeanMethodInvoker> asyncInitBeanMethodInvokerList = new ArrayList<>(256);
    private final ThreadPoolExecutor threadPoolExecutor;
    private final boolean collectBeanTime;
    private final int slowestBeanNum;
    private final Map<String, BeanInitTime> timeMap = new ConcurrentHashMap<>();

    public AsyncInitManager(AsyncInitProperties asyncInitProperties, AsyncInitProperties.InnerProperties innerProperties) {
        try {
            this.threadPoolExecutor = new ThreadPoolExecutor(asyncInitProperties.getThreadNum(), asyncInitProperties.getThreadNum(), 60, TimeUnit.SECONDS, new SynchronousQueue<>(), r -> {
                Thread t = new Thread(r);
                t.setName("AsyncInitThreadPool-" + t.getId());
                return t;
            }, new ThreadPoolExecutor.CallerRunsPolicy());
            this.collectBeanTime = asyncInitProperties.isCollectInitTime();
            this.slowestBeanNum = asyncInitProperties.getSlowestBeanNum();
            this.includeFilters = buildAsyncInitFilter(asyncInitProperties.getIncludes());
            this.innerIncludeFilters = buildAsyncInitFilter(innerProperties);
            this.excludeFilters = buildAsyncInitFilter(asyncInitProperties.getExcludes());
        } catch (ClassNotFoundException e) {
            throw new AsyncInitException(e);
        }
    }

    public synchronized AsyncInitBeanMethodInvoker addInvoker(Object bean, String beanName) {
        if (this.closeFlag.get()) {
            return null;
        }
        log.info("beanName=[{}]开始异步初始化", beanName);
        AsyncInitBeanMethodInvoker asyncInitBeanMethodInvoker = new AsyncInitBeanMethodInvoker(bean, beanName, threadPoolExecutor);
        asyncInitBeanMethodInvokerList.add(asyncInitBeanMethodInvoker);
        return asyncInitBeanMethodInvoker;
    }

    public void waitAllTaskFuture() {
        closeFlag.set(true);
        try {
            for (AsyncInitBeanMethodInvoker asyncInitBeanMethodInvoker : asyncInitBeanMethodInvokerList) {
                asyncInitBeanMethodInvoker.taskDone();
            }
        } catch (Throwable e) {
            throw new AsyncInitException(e);
        }
    }

    public boolean isEligibleSelf(Class<?> targetClass, String beanName) {
        if (this.closeFlag.get()) {
            return false;
        }
        Boolean eligible = null;
        if (!this.excludeFilters.isEmpty()) {
            boolean exclude = this.excludeFilters.stream().anyMatch(asyncFilter -> asyncFilter.match(targetClass, beanName));
            if (exclude) {
                eligible = false;
            }
        }
        if (eligible == null && !this.includeFilters.isEmpty()) {
            boolean include = this.includeFilters.stream().anyMatch(asyncFilter -> asyncFilter.match(targetClass, beanName));
            if (include) {
                eligible = true;
            }
        }
        if (eligible == null && !this.innerIncludeFilters.isEmpty()) {
            boolean include = this.innerIncludeFilters.stream().anyMatch(asyncFilter -> asyncFilter.match(targetClass, beanName));
            if (include) {
                eligible = true;
            }
        }
        if (eligible == null) {
            eligible = false;
        }
        return eligible;
    }


    public List<AsyncFilter> buildAsyncInitFilter(AsyncInitProperties.InnerProperties innerProperties) throws ClassNotFoundException {
        List<AsyncFilter> result = new ArrayList<>();
        if (innerProperties == null) {
            return result;
        }
        // beanName匹配
        if (innerProperties.getBeanNames() != null) {
            Set<String> beanNameSet = new HashSet<>(innerProperties.getBeanNames());
            result.add((targetCls, beanName) -> beanNameSet.contains(beanName));
        }
        // 如果注解不为空
        if (innerProperties.getAnnotations() != null) {
            for (String annoStr : innerProperties.getAnnotations()) {
                Class<?> clz = Class.forName(annoStr);
                if (!clz.isAnnotation()) {
                    throw new AsyncInitException(annoStr + "不是注解类型");
                }
                Class<? extends Annotation> annoType = (Class<? extends Annotation>) clz;
                result.add((targetCls, beanName) -> targetCls.isAnnotationPresent(annoType));
            }
        }
        // 如果类及其子类,或接口
        if (innerProperties.getRootClasses() != null) {
            for (String rootClassStr : innerProperties.getRootClasses()) {
                Class<?> rootClass = Class.forName(rootClassStr);
                result.add((targetCls, beanName) -> targetCls.isAssignableFrom(rootClass));
            }
        }
        // 表达式匹配
        if (innerProperties.getPackages() != null) {
            for (String packageStr : innerProperties.getPackages()) {
                result.add((targetCls, beanName) -> antPathMatcher.match(packageStr, targetCls.getPackage().getName()));
            }
        }
        return result;
    }

    public void clearAllResource() {
        if (!this.threadPoolExecutor.isShutdown()) {
            this.threadPoolExecutor.shutdown();
        }
        this.asyncInitBeanMethodInvokerList.clear();
        this.initClassCacheMap.clear();
        this.timeMap.clear();
    }

    public void startBeanInit(String beanName) {
        if (!this.collectBeanTime || this.closeFlag.get()) {
            return;
        }
        if (!timeMap.containsKey(beanName)) {
            timeMap.putIfAbsent(beanName, new BeanInitTime(beanName));
        }
    }

    public void finishedBeanInit(String beanName) {
        if (!this.collectBeanTime) {
            return;
        }
        BeanInitTime beanInitTime = timeMap.get(beanName);
        if (beanInitTime != null) {
            beanInitTime.end();
        }
    }


    public void printTimeCost() {
        if (!this.collectBeanTime) {
            return;
        }
        List<BeanInitTime> timeList = this.timeMap.values().stream()
                .filter(beanInitTime -> beanInitTime.getStart() != 0 && beanInitTime.getEnd() != 0)
                .sorted(Comparator.comparingLong(BeanInitTime::cost).reversed())
                .collect(Collectors.toList());
        log.info("================启动耗时打印开始 top{}==================", this.slowestBeanNum);
        for (int i = 0; i < timeList.size() && i < this.slowestBeanNum; i++) {
            log.info("beanName=[{}] 耗时:[{}]ms", timeList.get(i).getBeanName(), timeList.get(i).cost());
        }
        log.info("================启动耗时打印结束 top{}==================", this.slowestBeanNum);
    }

    public boolean doCache(Class<?> targetClass, BooleanSupplier supplier) {
        Boolean result = initClassCacheMap.get(targetClass);
        if (result != null) {
            return result;
        }
        result = supplier.getAsBoolean();
        initClassCacheMap.put(targetClass, result);
        return result;
    }
}
